﻿Option Strict On
Option Explicit On

''' <summary>
''' Defines a specific image as a frame within a source image asset.
''' </summary>
''' <remarks>
''' The most common way to display images on a GameObject is through the use of Sprites. A Sprite defines a single graphic file (Image)
''' and frame bounds (Rectangle) within that image. This allows a single image file to serve as a "sprite sheet" containing multiple
''' sprite images and/or multiple image frames of a single sprite. Each GameObject contains a Sprites collection. Any sprite instances
''' added to this collection will be updated and rendered by the GameObject. Each Sprite should only belong to one GameObject.Sprites
''' collection at a time, however, individual Sprite instances can all point to the same asset file. Sprites offer a PositionOffset
''' property which allow you to arrange them on a game object, and this accounts for each object requiring a unique sprite instance. 
''' There is no limit to the number of sprites which can be added to a single game object, however, performance should be considered due
''' to the extra draw operations, especially if scaling or rotation are used.
''' </remarks>
Public Class Sprite
    ''' <summary>
    ''' Creates a new sprite with the specified name, sprite sheet name, and frame bounds.
    ''' </summary>
    ''' <param name="spriteName">The name of the new sprite.</param>
    ''' <param name="imageSource">The name of the sprite sheet image resource to use.</param>
    ''' <param name="frame">The rectangle representing the bounds of the frame within the sprite sheet image.</param>
    ''' <returns>A new sprite with the specified name, sprite sheet name, and frame bounds.</returns>
    ''' <remarks></remarks>
    Public Shared Function CreateNew(spriteName As String, imageSource As String, frame As Rectangle) As Sprite
        Return New Sprite() With {.Name = spriteName, .SpriteSheetName = imageSource, ._FrameBounds = frame}
    End Function

    Private _UniqueId As Guid
    ''' <summary>
    ''' The sprite's globally unique identifier.
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property UniqueId As Guid
        Get
            Return _UniqueId
        End Get
    End Property

    Private _Name As String
    ''' <summary>
    ''' The friendly identifier for this sprite. Sprite names must be unique within
    ''' a given SpriteCollection.
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Property Name() As String
        Get
            Return _Name
        End Get
        Set(ByVal value As String)
            _Name = value
        End Set
    End Property

    Protected _SpriteSheetName As String = String.Empty
    ''' <summary>
    ''' The name of the sprite sheet image asset used by this sprite.
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Property SpriteSheetName() As String
        Get
            Return _SpriteSheetName
        End Get
        Set(ByVal value As String)
            If value Is Nothing Then value = String.Empty
            _SpriteSheetName = value
        End Set
    End Property

    Protected _FrameBounds As Rectangle
    ''' <summary>
    ''' Gets the rectangle bounds of this sprite's frame.
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Overridable ReadOnly Property FrameBounds() As Rectangle
        Get
            Return _FrameBounds
        End Get
    End Property

    Private _PositionOffset As Vector2
    ''' <summary>
    ''' The Sprite will be drawn at this offset from it's parent GameObject's center position.
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Property PositionOffset() As Vector2
        Get
            Return _PositionOffset
        End Get
        Set(ByVal value As Vector2)
            _PositionOffset = value
        End Set
    End Property

    Private _ZOrder As Integer
    ''' <summary>
    ''' Determines the order in which the Sprite is drawn to the screen.
    ''' Lower z-order values are drawn first, or underneath, higher z-order values.
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Property ZOrder() As Integer
        Get
            Return _ZOrder
        End Get
        Set(ByVal value As Integer)
            _ZOrder = value
        End Set
    End Property

    Public Sub New()
        _UniqueId = Guid.NewGuid
        _Name = _UniqueId.ToString
    End Sub

    ''' <summary>
    ''' Sets the rectangle bounds for the frame of this sprite.
    ''' </summary>
    ''' <param name="bounds"></param>
    ''' <remarks></remarks>
    Public Overridable Sub SetFrameBounds(ByVal bounds As Rectangle)
        _FrameBounds = bounds
    End Sub

    Protected Friend Overridable Sub OnDraw(ByVal e As SpriteDrawEventArgs)
        If _SpriteSheetName.Length > 0 Then
            Dim sourceBounds As RectangleF = _FrameBounds
            Dim spriteImage As Image = e.Engine.CurrentScene.GetImageAsset(_SpriteSheetName)
            Dim drawPoint As Vector2 = e.Object.Position 'Center of GameObject
            Dim targetBounds As New RectangleF(drawPoint.X, drawPoint.Y, sourceBounds.Size.Width * e.Object.Scale, sourceBounds.Size.Height * e.Object.Scale)
            e.PaintEventArgs.Graphics.TranslateTransform(drawPoint.X, drawPoint.Y)
            e.PaintEventArgs.Graphics.RotateTransform(e.Object.RotationAngle)
            e.PaintEventArgs.Graphics.TranslateTransform(-drawPoint.X, -drawPoint.Y)

            targetBounds.X -= CInt(targetBounds.Width / 2) + _PositionOffset.X
            targetBounds.Y -= CInt(targetBounds.Height / 2) + _PositionOffset.Y

            e.PaintEventArgs.Graphics.DrawImage(spriteImage, targetBounds, sourceBounds, GraphicsUnit.Pixel)
            e.PaintEventArgs.Graphics.ResetTransform()

            spriteImage = Nothing
        End If
    End Sub
End Class
